package com.minefinder;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TableRow;

public class GameActivity extends Activity {
	private static int MINE_HERE = 9;
	private static int NO_MINE_AROUND = 0;

	private static String SOUND_NOMINE = "nomine";
	private static String SOUND_CLICK = "click";
	private static String SOUND_MINE = "mine";
	private static String SOUND_GAMEOVER = "gameover";
	private static String SOUND_GAMECLEAR = "gameclear";
	
	private static float TABLEROW_LAYOUT_WEIGHT = 1f;

	private static int[] mineColorSet = { Color.rgb(255, 255, 255),
			Color.rgb(125, 125, 255), Color.rgb(125, 255, 125),
			Color.rgb(255, 125, 125), Color.rgb(125, 125, 0),
			Color.rgb(125, 0, 125), Color.rgb(0, 125, 125),
			Color.rgb(125, 125, 125), Color.rgb(0, 0, 0) };

	private int maxRow;
	private int maxColumn;
	private int maxMineCount;

	private double time;
	private String strTime;
	private int mineCount;
	private String strMineCount;
	private int selectedCount;

	private boolean isFirstClick;
	private boolean isGameEnd;

	private int[][] mineValues;

	private Timer mTimer;

	private EditText etTime;
	private EditText etMine;
	private Button btnReset;
	private TableLayout mineTableLayout;

	private SoundPool soundPool;
	private Map<String, Integer> soundPoolMap;
	private AudioManager audioManager;
	
	private List<Double> scoreList;
	private String dirPath;
	
	private String level;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_game);
		
		// set volume
		setVolumeControlStream(AudioManager.STREAM_MUSIC);
		
		// configuration
		Intent intent = getIntent();
		this.level = intent.getStringExtra(OptionsActivity.LEVEL_STRING);
		
		if (level.equals(OptionsActivity.LEVEL_EASY)) {
			this.maxRow = 9;
			this.maxColumn = 9;
			this.maxMineCount = 10;
		} else if (level.equals(OptionsActivity.LEVEL_NORMAL)) {
			this.maxRow = 16;
			this.maxColumn = 16;
			this.maxMineCount = 40;
		} else {
			this.maxRow = 30;
			this.maxColumn = 16;
			this.maxMineCount = 99;			
		}
		
		this.btnReset = (Button) findViewById(R.id.btn_reset);
		this.btnReset.setOnClickListener(new Button.OnClickListener() {
			public void onClick(View v) {
				reset();
			}
		});

		this.etMine = (EditText) findViewById(R.id.et_mine);
		this.etTime = (EditText) findViewById(R.id.et_time);

		this.mineTableLayout = (TableLayout) findViewById(R.id.mineTableLayout);
		
		this.scoreList = new ArrayList<Double>();		
		for (int i = 0; i < ScoreActivity.SCORE_MAX_COUNT; i++) {
			this.scoreList.add(ScoreActivity.INIT_SCORE);
		}
		
		this.dirPath = getFilesDir().getAbsolutePath();
		
		this.getScoreList();
		
		this.initSound();

		this.reset();
	}

	@Override
	public void onDestroy() {
		this.cancelTimer();
		this.soundPool.release();
		super.onDestroy();
	}

	// sound
	private void initSound() {
		this.soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
		this.audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
		this.soundPoolMap = new HashMap<String, Integer>();

		this.soundPoolMap.put(SOUND_NOMINE,
				soundPool.load(getApplicationContext(), R.raw.nomine, 1));
		this.soundPoolMap.put(SOUND_CLICK,
				soundPool.load(getApplicationContext(), R.raw.click, 1));
		this.soundPoolMap.put(SOUND_MINE,
				soundPool.load(getApplicationContext(), R.raw.mine, 1));
		this.soundPoolMap.put(SOUND_GAMEOVER,
				soundPool.load(getApplicationContext(), R.raw.gameover, 1));
		this.soundPoolMap.put(SOUND_GAMECLEAR,
				soundPool.load(getApplicationContext(), R.raw.gameclear, 1));
	}

	public void playSound(String key) {
		float sv = this.audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
		// resource, left volume, right volume, priority, replay, play speed.
		this.soundPool.play(this.soundPoolMap.get(key), sv, sv, 0, 0, 1f);
	}

	// timer
	private void initTimer() {
		if (mTimer == null) {
			mTimer = new Timer();
			mTimer.schedule(new MyTimerTask(), 0, 10);
			//Log.i("info", "mTimer init.");
		}
	}

	private void cancelTimer() {
		if (mTimer != null) {
			mTimer.cancel();
			mTimer.purge();
			mTimer = null;

			//Log.i("info", "Timer stoped.");
		}
	}

	class MyTimerTask extends TimerTask {
		public void run() {
			try {
				time = time + 0.01;
				strTime = String.format("%3.2f", time);
				etTime.post(setTimeRunnable);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};

	Runnable setTimeRunnable = new Runnable() {
		public void run() {
			etTime.setText(strTime);
			//Log.i("info", "time: " + strTime);
		}
	};

	private void getScoreList() {
		File scoreDir = new File(this.dirPath);
		File scoreFile = new File(this.dirPath + "/" + this.level + ScoreActivity.SCORE_FILE_POSTFIX);
		
		if (!scoreDir.exists()) {
			scoreDir.mkdirs();
		}
		
		if (!scoreFile.exists()) {
			try {
				scoreFile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			this.writeScoreToFile();
			//Log.i("info", "create new file end!!");
		}
		
		try {
			FileInputStream fis = new FileInputStream(scoreFile);
			BufferedReader br = new BufferedReader(new InputStreamReader(fis));
			
			String line = br.readLine();
			while (line != null) {
				double score = Double.parseDouble(line);
				this.scoreList.add(score);
				
				line = br.readLine();
			}
			
			br.close();
			fis.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
			
		Collections.sort(this.scoreList);
	}

	private void writeScoreToFile() {
		File scoreFile = new File(this.dirPath + "/" + this.level + ScoreActivity.SCORE_FILE_POSTFIX);
		
		try {			
			FileOutputStream fos = new FileOutputStream(scoreFile);
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
			
			for (int i = 0; i < ScoreActivity.SCORE_MAX_COUNT; i++) {
				String scoreStr = "" + this.scoreList.get(i);
				//Log.i("info", scoreStr);
				bw.write(scoreStr);
				
				if (i != ScoreActivity.SCORE_MAX_COUNT - 1) {
					bw.newLine();
				}
			}
			
			bw.close();			
			fos.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void reset() {
		// reset timer
		this.cancelTimer();

		this.isFirstClick = true;
		this.isGameEnd = false;

		this.time = 0.0;
		this.strTime = "0.00";

		this.mineCount = this.maxMineCount;
		this.strMineCount = String.valueOf(this.mineCount);

		this.selectedCount = this.maxRow * this.maxColumn;

		this.etMine.setText(this.strMineCount);
		this.etTime.setText(this.strTime);

		this.mineTableLayout.removeAllViews();
		this.mineTableLayout.setOrientation(TableLayout.HORIZONTAL);
		this.mineTableLayout.setShrinkAllColumns(true);
		this.mineTableLayout.setBackgroundColor(Color.BLACK);

		// set random mine position
		this.setMine();

		// set view
		this.setMineView();

	}

	private void setMine() {
		int settedMineCount = 0;
		this.mineValues = new int[this.maxRow][this.maxColumn];

		while (settedMineCount < this.maxMineCount) {
			int random = (int) ((Math.random() * this.maxRow * this.maxColumn));
			
			int row = this.getRow(random);
			int col = this.getCol(random, row);
			if (row >= this.maxRow || col >= this.maxColumn) {
				continue;
			}

			if (this.mineValues[row][col] != MINE_HERE) {
				this.mineValues[row][col] = MINE_HERE;
				settedMineCount++;
			}
		}

		for (int i = 0; i < this.maxRow; i++) {
			for (int j = 0; j < this.maxColumn; j++) {
				if (this.mineValues[i][j] != MINE_HERE) {
					int mineCount = 0;
					for (int k = i - 1; k <= i + 1; k++) {
						for (int l = j - 1; l <= j + 1; l++) {
							if (k >= 0 && k < this.maxRow && l >= 0
									&& l < this.maxColumn
									&& this.mineValues[k][l] == MINE_HERE)
								mineCount++;
						}
					}
					this.mineValues[i][j] = mineCount;
				}
			}
		}
	}

	private void setMineView() {
		
		for (int i = 0; i < this.maxRow; i++) {
			TableRow tr = new TableRow(this);
			
			for (int j = 0; j < this.maxColumn; j++) {
				Button btn = new Button(this);
				btn.setId(this.getId(i, j));
				btn.setBackgroundResource(R.drawable.mine_button);

				btn.setOnLongClickListener(btnLongClickListener);
				btn.setOnClickListener(btnClickListener);
				btn.setLongClickable(true);
				btn.setPadding(0, 0, 0, 0);
				
				if (this.mineValues[i][j] != MINE_HERE) {
					btn.setTextColor(mineColorSet[this.mineValues[i][j]]);
				}
				
				tr.addView(btn, new TableRow.LayoutParams(
						LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, TABLEROW_LAYOUT_WEIGHT));
			}
			this.mineTableLayout.addView(tr, new TableLayout.LayoutParams(
					LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, TABLEROW_LAYOUT_WEIGHT));
		}
	}

	private OnLongClickListener btnLongClickListener = new OnLongClickListener() {
		public boolean onLongClick(View v) {
			// first click
			if (isFirstClick == true) {
				isFirstClick = false;
				initTimer();
				//Log.i("info", "first click. init Timer.");
			}

			Button btn = (Button) v;
			if (isGameEnd == true)
				return false;

			int id = btn.getId();
			int row = getRow(id);
			int col = getCol(id, row);

			if (btn.isSelected() == false) {
				//Log.i("info", id + " btn long clicked.");
				playSound(SOUND_CLICK);
				setButtonLongClicked(row, col);
				return true;
			}

			return false;
		}
	};

	private OnClickListener btnClickListener = new OnClickListener() {
		public void onClick(View v) {
			// first click
			if (isFirstClick == true) {
				isFirstClick = false;
				initTimer();
				//Log.i("info", "first click. init Timer.");
			}

			Button btn = (Button) v;
			if (isGameEnd == true)
				return;

			int id = btn.getId();
			int row = getRow(id);
			int col = getCol(id, row);
			int mineNum = mineValues[row][col];
			//Log.i("info", "btn id: " + id + ", row: " + row + ", col: "
			// + col + ", mine: " + mineNum);

			if (btn.isLongClickable() == false) {
				playSound(SOUND_CLICK);
				setButtonLongClicked(row, col);
				return;
			}
			if (btn.isSelected() == false) {
				if (mineNum == MINE_HERE) {
					//Log.i("info", "MINE btn selected.");
					playSound(SOUND_MINE);
					gameOver();
					playSound(SOUND_GAMEOVER);
				} else if (mineNum == NO_MINE_AROUND) {
					//Log.i("info", "no mine around btn selected.");
					playSound(SOUND_NOMINE);
					clearNoMines(row, col);
				} else {
					//Log.i("info", "btn selected.");
					playSound(SOUND_CLICK);
					setButtonSelected(row, col);
				}

				// check clear
				checkClear();
			}
		}
	};

	private void setButtonLongClicked(int row, int col) {
		int id = this.getId(row, col);

		Button btn = (Button) findViewById(id);
		if (btn.isLongClickable() == false) {
			//Log.i("info", "set btn normal.");
			this.mineCount++;
			btn.setBackgroundResource(R.drawable.icon);
			btn.setLongClickable(true);
		} else {
			//Log.i("info", "set btn long clicked.");
			this.mineCount--;
			btn.setBackgroundResource(R.drawable.flag_icon);
			btn.setLongClickable(false);
		}

		btn.invalidate();

		this.strMineCount = String.format("%d", this.mineCount);
		this.etMine.setText(this.strMineCount);
		this.etMine.invalidate();
	}

	private void setButtonSelected(int row, int col) {
		int id = this.getId(row, col);
		Button btn = (Button) findViewById(id);
		if (btn.isSelected() == true || btn.isLongClickable() == false)
			return;

		int mineNum = this.mineValues[row][col];
		if (mineNum == MINE_HERE) {
			btn.setBackgroundResource(R.drawable.mine_icon);
		} else {
			this.selectedCount--;

			btn.setText(String.valueOf(this.mineValues[row][col]));
			
			btn.setBackgroundResource(R.drawable.selected_icon);
			btn.setSelected(true);
		}
		btn.invalidate();
	}

	private void clearNoMines(int row, int col) {
		this.setButtonSelected(row, col);
		//Log.i("info", "i: " + row + ", j: " + col);

		for (int i = row - 1; i <= row + 1; i++) {
			for (int j = col - 1; j <= col + 1; j++) {
				if (i >= 0 && i < this.maxRow && j >= 0 && j < this.maxColumn
						&& !(i == row && j == col)) {
					if (this.mineValues[i][j] == NO_MINE_AROUND) {
						Button btn2 = (Button) findViewById(this.getId(i, j));
						if (btn2.isSelected() == false)
							clearNoMines(i, j);
					} else if (this.mineValues[i][j] != MINE_HERE)
						this.setButtonSelected(i, j);
				}
			}
		}
	}

	private void checkClear() {
		if (this.selectedCount == this.maxMineCount) {
			this.gameClear();
		}
	}

	private void gameClear() {
		this.cancelTimer();
		this.isGameEnd = true;
		this.playSound(SOUND_GAMECLEAR);
		
		double score = Double.parseDouble(this.strTime);
		
		boolean isNewRecord = this.checkNewScore(score);
		
		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
		dlg.setTitle("You Win!");
		
		if (isNewRecord) {
			dlg.setMessage("New Record!! clear time: " + score + "sec.");			
		} else {
			dlg.setMessage("clear time: " + score + "sec.");			
		}
		dlg.setNegativeButton("Close", null);
		dlg.show();
	}

	private boolean checkNewScore(double newScore) {
		boolean isNewRecord = false;
		
		for (Double bestScore : this.scoreList) {
			if (newScore <= bestScore) {				
				isNewRecord = true;				
				break;
			}
		}
		
		if (isNewRecord) {
			this.scoreList.add(newScore);
			Collections.sort(this.scoreList);
			this.scoreList.remove(ScoreActivity.SCORE_MAX_COUNT);
			
			this.writeScoreToFile();
		}
		
		return isNewRecord;
	}

	private void gameOver() {
		this.cancelTimer();
		this.isGameEnd = true;

		for (int i = 0; i < this.maxRow; i++) {
			for (int j = 0; j < this.maxColumn; j++) {
				if (this.mineValues[i][j] == MINE_HERE) {
					Button btn = (Button) findViewById(this.getId(i, j));
					btn.setLongClickable(true);
					this.setButtonSelected(i, j);
				}
			}
		}

		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
		dlg.setTitle("Game Over");
		dlg.setMessage("OTL...T^T");
		dlg.setNegativeButton("Close", null);
		dlg.show();
		
	}

	private int getRow(int id) {
		return id / this.maxColumn;
	}

	private int getCol(int id, int row) {
		return id - row * this.maxColumn;
	}

	private int getId(int row, int col) {
		return row * this.maxColumn + col;
	}
}
